package org.snlab.runtime;

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.protobuf.ByteString;

import org.snlab.proto.NotifyGrpc;
import org.snlab.proto.Ddmpt.Empty;
import org.snlab.proto.Ddmpt.Entry;
import org.snlab.proto.Ddmpt.Msg;
import org.snlab.proto.Ddmpt.Nexthop;
import org.snlab.proto.Ddmpt.VerifyTask;
import org.snlab.proto.Ddmpt.Vroutes;
import org.snlab.proto.NotifyGrpc.NotifyBlockingStub;
import org.snlab.proto.NotifyGrpc.NotifyImplBase;
import org.snlab.proto.RuntimeGrpc.RuntimeImplBase;

import io.grpc.Channel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

public class GRPCServer {
    private Daemon daemon;
    private Server server;
    private int port;

    public GRPCServer(Daemon daemon) {
        this.daemon = daemon;
        this.port = daemon.getConfig().getPort();
    }

    public void start() throws InterruptedException {
        server = ServerBuilder.forPort(this.port).addService(new RuntimeImpl(daemon))
                .build();
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                GRPCServer.this.stop();
            }
        });
        try {
            server.start();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            this.stop();
            System.exit(1);
        }
        server.awaitTermination();
    }

    public void stop() {
        if (server != null) {
            server.shutdown();
        }
    }

    // public void run() {
    // server = ServerBuilder.forPort(this.port).addService(new
    // DdmImpl(daemon)).build();
    // Runtime.getRuntime().addShutdownHook(new Thread() {
    // @Override
    // public void run() {
    // GRPCServer.this.stop();
    // }
    // });
    // // try {
    // // server.awaitTermination();
    // // } catch (InterruptedException e) {
    // // e.printStackTrace();
    // // }
    // }
}

// class DdmImpl extends NotifyImplBase {
// private Daemon daemon;

// public DdmImpl(Daemon daemon) {
// this.daemon = daemon;
// }
// }

// class RuntimeImpl extends RuntimeImplBase {
//     private Daemon daemon;

//     public RuntimeImpl(Daemon daemon) {
//         this.daemon = daemon;
//     }

//     private void doMap(int hs, List<Nexthop> nexthops) {
//         BDDEngine bddEngine = daemon.getDIB().getBDDEngine();
//         for (Nexthop nexthop : nexthops) {
//             for (String port : nexthop.getViaList()) {
//                 int portHs = daemon.getDIB().getPortToPred().getOrDefault(port, 0);
//                 int intersection = daemon.getDIB().getBDDEngine().and(hs, portHs);
//                 if (intersection != 0) {
//                     ByteString bs = ByteString.copyFrom(bddEngine.serialize(intersection));
//                     Msg m = Msg.newBuilder().setHs(bs).setTo(nexthop.getVid()).build();

//                     String[] addrArr = nexthop.getAddr().split(":");
//                     Channel c = ManagedChannelBuilder.forAddress(addrArr[0], Integer.parseInt(addrArr[1]))
//                             .usePlaintext().build();
//                     NotifyBlockingStub stub = NotifyGrpc.newBlockingStub(c);
//                     stub.process(m);

//                     // TODO: manage the channels, check grpc reuse managed channels
//                     // TODO: reply first then send msg to downstreams
//                 }
//             }
//         }
//     }

//     private void processMsg(Msg request) {
//         BDDEngine bddEngine = daemon.getDIB().getBDDEngine();
//         List<Nexthop> nexthops = daemon.getRouter().route(request.getTo());
//         if (nexthops.size() == 0) {
//             Daemon.logger.log("reach dst " + daemon.getConfig().getName());
//         } else {
//             int hs = bddEngine.deserialize(request.getHs().toByteArray());
//             this.checkViolation(hs, nexthops);
//             doMap(hs, nexthops);
//         }
//     }

//     @Override
//     public void process(Msg request, StreamObserver<Empty> responseObserver) {
//         System.out.println("received vid: " + request.getTo());

//         Empty rp = Empty.newBuilder().build();
//         responseObserver.onNext(rp);
//         responseObserver.onCompleted();
//         processMsg(request);
//     }

//     private void checkViolation(int hs, List<Nexthop> nexthops) {
//         BDDEngine bddEngine = daemon.getDIB().getBDDEngine();
//         if (nexthops.size() == 0) {
//             return;
//         }
//         int outHs = 0;
//         for (Nexthop nexthop : nexthops) {
//             for (String port : nexthop.getViaList()) {
//                 System.out.println(port);
//                 int h = daemon.getDIB().getPortToPred().getOrDefault(port, 0); // in case no rules covers a port, e.g.
//                                                                                // I2 wash xe-0/0/0
//                 outHs = daemon.getDIB().getBDDEngine().or(outHs, h);
//             }
//         }
//         if (!bddEngine.subset(hs, outHs)) {
//             System.out.print("violation");
//             Daemon.logger.log("found violation");
//         }
//     }

//     @Override
//     public void insertEntry(Entry request, StreamObserver<Empty> responseObserver) {
//         daemon.getRouter().insert(request.getVid(), request.getNexthopsList());
//         responseObserver.onNext(Empty.newBuilder().build());
//         responseObserver.onCompleted();
//     }

//     @Override
//     public void showVroutes(Empty request, StreamObserver<Vroutes> responseObserver) {
//         Vroutes.Builder vroutes = Vroutes.newBuilder();
//         for (Map.Entry<Long, List<Nexthop>> e : daemon.getRouter().getTable().entrySet()) {
//             Entry entry = Entry.newBuilder().setVid(e.getKey()).addAllNexthops(e.getValue()).build();
//             vroutes.addEntires(entry);
//         }
//         Vroutes vrs = vroutes.build();
//         responseObserver.onNext(vrs);
//         responseObserver.onCompleted();
//     }

//     @Override
//     public void verify(VerifyTask request, StreamObserver<org.snlab.proto.Ddmpt.Empty> responseObserver) {
//         Daemon.logger.log("start propagation");
//         BDDEngine bddEngine = daemon.getDIB().getBDDEngine();
//         int hs = bddEngine.encodeIP(request.getIp(), request.getPrefix());
//         List<Nexthop> nexthops = daemon.getRouter().route(request.getVid());
//         checkViolation(hs, nexthops);
//         doMap(hs, nexthops);

//         // ByteString bs = ByteString.copyFrom(bddEngine.serialize(hs));
//         // Msg m = Msg.newBuilder().setHs(bs).setVid(request.getVid()).build();

//         // Channel c = ManagedChannelBuilder.forAddress("localhost", daemon.getConfig().getPort()).usePlaintext().build();
//         // NotifyBlockingStub stub = NotifyGrpc.newBlockingStub(c);
//         // stub.process(m);
//         // responseObserver.onNext(org.snlab.proto.Ddmpt.Empty.newBuilder().build());
//         // responseObserver.onCompleted();
//     }
// }